
#ifndef _K_MEM_MMU_C_
#define _K_MEM_MMU_C_

#include "k_mem_sysmmu.h"
#include "../k_common_asm.h"

/**
 * @BRIEF	:	Initialize OS MMU
 * @PARAM	:	void
 * @RETURN	:	void
 * @NOTE	:	OS will map K_SYS_MEM_VIR_SIZE(defined in file basedefine.h) physical space
 * 			:	Physical address and virtual address are mapped One to One in fixed order!
 * 			: =>We should keep the order when we allocating pages for OS!
 */
void k_mem_mmu_initialize()
{
	int i, j;
	unsigned int *p_fst;
	unsigned int *p_sec;
	int	n_slpt_rcd_count;
	int n_slpt_io_rcd_count;

	/**
		we use small page.So:
		1.every first level page table record map 1MB space
		2.every second level page table record map 4K space

		More information see ARM TECH DOCs
	*/

	/**
	 * NOTE:	At this time, MMU mode are still disabled!
	 * 		So, all operations are operated in real address!
	 * 	  =>Therefore, we can use the macros defined!
	 */
	p_fst = (unsigned int*)k_mem_mmu_flpt_os;
	p_sec = (unsigned int*)k_mem_mmu_slpt_os;

	/** fill First Level Page Table of OS **/
	for(i = 0; i < K_SYS_MEM_VIR_SIZE; i++)
	{
		p_fst[i] = ( ( k_mem_mmu_slpt_os + (i << 10) ) & 0xfffffc00 ) | 0x31;
	}

	/* fill I/O mapping First Level Page Table of OS */
	for(i = K_SYS_MEM_VIR_SIZE, j = 0; i < K_SYS_MEM_VIR_SIZE + K_SYS_IO_SPACE_SIZE; i++, j++)
	{
		p_fst[i] = ( ( k_mem_mmu_slpt_io_mapping + (j << 10) ) & 0xfffffc00 ) | 0x31;
	}

	/** fill Second Level Page Table of OS **/
	/**
	 * We can calculate the count of Second Level Page Table Records in all
	 * used by OS.
	 */
	n_slpt_rcd_count = K_SYS_MEM_VIR_SIZE << 8;
	for(i = 0; i < n_slpt_rcd_count; i++)
	{
		p_sec[i] = ((K_MEM_PHY_PMTIDX2PHYMMADDR(g_s_k_mem_phy_pagestart + i) & 0xfffff000)|0x552)|(K_MEM_MMU_WRITE_MODE_WB <<3)|(K_MEM_MMU_WRITE_MODE_B<<2);
	}

	/**
	 * fill I/O mapping space Second Level Page Tables
	 */
	p_sec = (unsigned int *)k_mem_mmu_slpt_io_mapping;

	n_slpt_io_rcd_count = K_SYS_IO_SPACE_SIZE << 8;
	for(i = 0; i < n_slpt_io_rcd_count; i++)
	{
		// don't use any cache! because this is I/O
		p_sec[i] = ((K_MEM_PHY_PMTIDX2PHYMMADDR(K_MEM_PHY_PHYMMADDR2PMTIDX(K_SYS_IO_PHYSICAL_START_ADDR) + i) & 0xfffff000)|0x552)|(0 <<3)|(0<<2);
	}
}

/**
 * @BRIEF	:	Allocate Virtual Pages
 * @PARAM	:	vm_mode			the mode used when allocating pages
 * @PARAM	:	page_count		count of pages will be allocated
 * @RETURN	:	virtual address of the first allocated virtual page
 * @NOTE	:	OS will map K_SYS_MEM_VIR_SIZE(defined in file basedefine.h) physical space
 * 			:	Physical address and virtual address are mapped One to One in fixed order!
 * 			: =>We should keep the order when we allocating pages for OS!
 * 			:
 * @INT		:	AUTO(disable and enable)
 */
void* k_mem_mmu_allocate(EN_K_MEM_PHY_USE_MODE vm_mode, unsigned int page_count)
{
	if(page_count == 0)
		return 0;

	/**
	 * if vm_mode is SYSTEM mode, we just allocate physical
	 * and change the physical address to virtual address!
	 */
	if(vm_mode == K_MEM_PHY_SYSTEM)
	{
		// we should disable interrupt!
		void *addr;
		void *vir_addr;
		K_MEM_DISABLE_INTERRUPT
		addr = k_mem_phy_allocate(vm_mode, page_count);
		K_MEM_ENABLE_INTERRUPT

		if(addr == 0)
		{
			// failed to allocate physical page
			return 0;
		}

		// change physical address to virtual address
		vir_addr = (void *)K_MEM_MMU_SYS_PHY2VIR((unsigned int)addr);
		return vir_addr;
	}
	else
	{
		/**
		 * We should scan the user virtual space to find continuous
		 * virtual address.And the others are easy!
		 */
		/**
		 * We should disable interrupt when scanning.Because,OS sechedu thread not process.
		 * Threads share the same heap.And heap are managed by OS.It means, Process allocate
		 * memory directly by OS.
		 */
		/** first, we get the FLPT(First Level Page Table) virtual address of current task! */
		unsigned int *p_fst = (unsigned int *)K_MEM_MMU_SYS_PHY2VIR(g_p_cur_process->fsttb_phy_addr);

		unsigned int *p_sec;
		unsigned int tmp_cnt = 0;
		/** Scan the FLPT and SLPT in USER space **/
		/**
		 * User space is started from K_SYS_MEM_VIR_SIZE (defined in file basedefine.h)
		 */
		unsigned int f_idx = K_SYS_PROCESS_HEAPSTACKSTART_START;
		unsigned int i,j;
		for(i = f_idx; i < K_SYS_PROCESS_HEAPSTACKSTART_END; i++)
		{
			// disable interrupt
			K_MEM_DISABLE_INTERRUPT

			// check whether current FLPT record has SLPT!
			if(p_fst[i] & 0x01)
			{
				// it has SLPT!, now we should scan SLPT of current FLPT record.
				p_sec = (unsigned int *)K_MEM_MMU_SYS_PHY2VIR(p_fst[i] & 0xfffffc00);

				for(j = 0; j < 256; j++)	// every SLPT record has 256 records.
				{
					// check whether current SLPT record are used!
					K_MEM_DISABLE_INTERRUPT;
					if(!p_sec[j] & 0x02)
					{
						// it has not been used!
						tmp_cnt++;
					}
					else
					{
						tmp_cnt = 0;
						K_MEM_ENABLE_INTERRUPT;
						continue;
					}

					if(tmp_cnt == page_count)
					{
						// we got it ^_^
						/** here we can get only virtual address, but that's enough^_^*/
						unsigned int cur_vir_addr = (i << 20)|(j << 12);
						unsigned int start_vir_addr = cur_vir_addr - ((page_count - 1) << 12);	/** we should sub 1 ^_^ */
						unsigned int i_tmp;
						unsigned int *p_newpage;

						// now, we got the new start virtual address.
						// fill them(SLPT records).
						for(i_tmp = 0; i_tmp < page_count; i_tmp++)
						{
							/**
							 * @@@ 	=>We just need to allocate physical page one by one
							 * 		  We can't use continuous physical pages!Because this
							 * 		  is USER mode.Virutal address will be continuous!
							 * @@@  <=
							 */
							p_newpage = k_mem_phy_allocate(vm_mode, 1);	// we allocate 1, because this is USER.
																		// virtual address will be continuous!
							if(p_newpage == 0)
							{
								// dim,we can't allocate new physical page.
								// we'd better to free pages we have allocated.
								// we should tell process we have not enough physical memory!
								unsigned int n_idx;
								for(n_idx = 0; n_idx < i_tmp; n_idx++)
								{
									// free the physical page and clear the SLPT record.
									unsigned int addr_phy;
									K_MEM_MMU_GET_PHYADDR((start_vir_addr + (n_idx << 12)), addr_phy, p_fst);

									k_mem_phy_free(addr_phy);
									K_MEM_MMU_FREE_VIR((start_vir_addr + (n_idx << 12)), p_fst);
								}

								// enable interrupt and return fail
								K_MEM_ENABLE_INTERRUPT;
								return (void *)0;
							}
							// fill the new page frame
							K_MEM_MMU_FILL_SMPT(((unsigned int)p_newpage), (start_vir_addr + (i_tmp << 12)),
									p_fst, K_MEM_MMU_AP_USER_READWRITE, K_MEM_MMU_WRITE_MODE_WB,
									K_MEM_MMU_WRITE_MODE_B)
						}

						// enable interrupt
						K_MEM_ENABLE_INTERRUPT;
						return (void *)start_vir_addr;
					}
				}
			}
			else
			{
				/** the current FLPT record is not mapped.. so, we try to map a SLPT to it, and re-find*/
				/** running here, it's means the interrupt has been disabled! we don't need to disable it again */

				// now we should allocate a new physical page from OS physical space.
				void *p_newslpt = k_mem_phy_allocate(K_MEM_PHY_SYSTEM, 1);
				unsigned int f_tmp = 0;
				unsigned int n_newslpt = (unsigned int)p_newslpt;
				unsigned int n_clear,n_clear_size;
				unsigned int *p_clear_tmp;

				if(p_newslpt == 0)
				{
					K_MEM_ENABLE_INTERRUPT;
					return 0;
				}

				/** =>first ,we should clear the memory! **/
				n_clear_size = K_VAL_PAGESIZE >> 2;
				p_clear_tmp = (unsigned int *)(K_MEM_MMU_SYS_PHY2VIR((unsigned int)p_newslpt));
				for(n_clear = 0; n_clear < n_clear_size; n_clear++)
				{
					p_clear_tmp[n_clear] = 0;
				}

				// fill SLPT recode to FLPT
				/** 4 FLPT records can be mapped per physical page */
				for(f_tmp = 0; f_tmp < 4; f_tmp++)
				{
					K_MEM_MMU_FILL_FMPT((n_newslpt + (f_tmp << 10)), ((i + f_tmp) << 20), K_MEM_MMU_DOMAIN_SYSTEM, (unsigned int)p_fst)
				}

				// Adjustment i,and re-find current FLPT record!
				i--;
			}
		}

		// we can't allocate new page
		return 0;
	}
}

/**
 * @BRIEF	:	Allocate First Level Page Table
 * @PARAM	:	void
 * @RETURN	:	physical address of FLPT
 * @NOTE	: =>This function will allocate new FLPT and fill it with
 * 			: =>System Space FLPT Record.
 * 			: =>This function return PHYSICAL ADDRESS!
 */
void* k_mem_mmu_allocate_flpt_return_physical_address(void)
{
	void *p_vir_newflpt;
	void *p_newflpt;

	unsigned int *p_clear;
	unsigned int n_clear, n_clear_size;
	unsigned int *p_fst;

	/** we allocate new FLPT, it's special---16KB bound **/
	K_MEM_DISABLE_INTERRUPT					// we should disable interrupt
	p_newflpt = k_mem_phy_allocate_flpt();
	K_MEM_ENABLE_INTERRUPT;						// enable interrupt

	p_vir_newflpt = (void *)K_MEM_MMU_SYS_PHY2VIR((unsigned int)p_newflpt);


	if(p_newflpt == 0)
	{
		// dim, we failed
		return 0;
	}

	/** we should clear the new physical page memory **/
	n_clear_size = (1 << 14) >> 2;
	p_clear = (unsigned int *)p_vir_newflpt;
	for(n_clear = 0; n_clear < n_clear_size; n_clear++)
	{
		p_clear[n_clear] = 0;
	}

	/** fill the new FLPT with system space FLPT records **/
	n_clear_size = K_SYS_MEM_VIR_SIZE;	// record count is: K_SYS_MEM_VIR_SIZE.
	p_fst = (unsigned int*)k_mem_mmu_flpt_os;
	for(n_clear = 0; n_clear < n_clear_size; n_clear++)
	{
		p_clear[n_clear] = p_fst[n_clear];
	}

	/** file the new FLPT with system I/O mapping space FLPT records */
	n_clear_size = K_SYS_MEM_VIR_SIZE + K_SYS_IO_SPACE_SIZE;
	p_fst = (unsigned int*)k_mem_mmu_flpt_os;
	for(n_clear = K_SYS_MEM_VIR_SIZE; n_clear < n_clear_size; n_clear++)
	{
		p_clear[n_clear] = p_fst[n_clear];
	}

	return p_newflpt;
}

/**
 * @BRIEF	:	Free First Level Page Table
 * @PARAM	:	phy_addr    physical address of FLPT
 * @RETURN	:	physical address of FLPT
 * @NOTE	: =>This function will free FLPT we don't need to free the
 * @WARNING : =>virtual space, because it's OS space, and can't change
 *          : =>any for ever.
 */
void k_mem_mmu_free_flpt_with_physical_address(unsigned int phy_addr)
{
    // there's 4 pages per flpt.so, we need free 4 pages
    int i = 0;

    // first we check whether this physical address is 16KB bound
    if(phy_addr & 0xffffc000)
    {
        // we this address is not a flpt.we can't free it
        return;
    }

    for(i = 0; i < 4; i++)
    {
        k_mem_phy_free(phy_addr + (i << 12));
    }
}

/**
 * @BRIEF	:	Free Virtual Pages
 * @PARAM	:	vir_addr		virtual page will be freed
 * @RETURN	:	virtual address of the first allocated virtual page
 * @NOTE	: =>This function will just free one virtual page.If wanting more,
 * 			:	we should call this repeatedly.
 */
void k_mem_mmu_free(void *vir_addr)
{
	// free the physical page. And clear the SLPT record if this virtual address
	// is not belonging to OS virtual space!
	unsigned int addr_phy;
	unsigned int *p_fst = (unsigned int *)g_p_cur_process->fsttb_phy_addr;

	// get the physical address of current virtual address
	K_MEM_MMU_GET_PHYADDR((unsigned int)vir_addr, addr_phy, p_fst);

    // first free the physical page
	k_mem_phy_free(addr_phy);

	// $WARNING$, we can't free virtual address if this is OS virtual space.
	// because we must keep OS' virtual address validable for ever!
	if((unsigned int)vir_addr < (( K_SYS_MEM_VIR_SIZE + K_SYS_IO_SPACE_SIZE ) << 20))
	{
	    // this virtual address is belonging to OS, so we can't free the
	    // virtual address mapped into.
	    return;
    }
    else
	{
	    // this vitual address is belonging to USER, we must free the
	    // virtual address space
	    K_MEM_MMU_FREE_VIR((unsigned int)vir_addr , p_fst);
    }
}

/**
 * @BRIEF	:	Free Process all space including ALL USER SPACE SLPT but except FLPT!
 * @PARAM	:	FLPT virtual address    virtual address of process flpt
 * @NOTE    :   we should not free OS SLPT, because we wouldn't allocate OS page mapping into OS SLPT.It's always exist!
 */
void k_mem_mmu_freeprocessusrspace(unsigned int *addr_flpt)
{
    // scan flpt from usr space
    unsigned int i,j;
    unsigned int *p_flpt = addr_flpt;

    for(i = K_SYS_PROCESS_CODECONST_START; i < K_SYS_PROCESS_HEAPSTACKSTART_END; i++)
    {
        if(p_flpt[i] & 0x01)
        {
            // deeply in to SLPT
            unsigned int *p_slpt = (unsigned int *)K_MEM_MMU_SYS_PHY2VIR(p_flpt[i] & 0xfffffc00);
            for(j = 0; j <K_VAL_SECCPERFST; j++)
            {
                if(p_slpt[j] & 0x02)
                {
                    // free current page
                    k_mem_phy_free(p_slpt[j] & 0xfffff000);
                }
            }
            // free user space SLPT
            k_mem_mmu_free(K_MEM_MMU_SYS_PHY2VIR(p_slpt));
        }
    }
}


#endif // _K_MEM_MMU_C_
